/*
 * Copyright 2009-2010 the original author or authors.
 *
 * Licensed under the Apache license, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.internna.iwebjtracker.spring.mvc;

import java.util.ArrayList;
import java.util.Set;
import java.util.HashSet;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import org.internna.iwebjtracker.issue.model.Issue;
import org.internna.iwebjtracker.project.model.IWebJTrackerUser;
import org.internna.iwebjtracker.project.model.Project;
import org.internna.iwebjtracker.services.TrackerUserManager;
import org.internna.iwebmvc.crypto.Cipherer;
import org.internna.iwebmvc.dao.DAO;
import org.internna.iwebmvc.model.UUID;
import org.internna.iwebmvc.utils.Assert;
import org.internna.iwebmvc.crypto.Decipherer;
import org.internna.iwebmvc.model.DomainEntity;
import org.internna.iwebmvc.model.ui.Filter;
import org.internna.iwebmvc.utils.CollectionUtils;
import org.springframework.ui.ModelMap;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * Accepts requests to show an issue main page.
 *
 * @author Jose Noheda
 * @since 1.0
 */
@Controller
@RequestMapping("/issue.iwebmvc")
public final class IssueController<T extends Issue<?, ?, ?, ?, ?, T> & DomainEntity> {

    public static final String ENTITY = "ENTITY";
    public static final String ISSUE = "issue";
    public static final String ISSUE_VOTES = "issueVotes";
    public static final String ISSUE_CLOSED = "issueClosed";
    public static final String ISSUE_FILTERS = "issueFilters";
    public static final String ISSUE_WATCHERS = "issueWatchers";
    public static final String ISSUE_LAST_UPDATE = "lastChange";

    /**
     * Key (JSP) for checking the current user is an administrator.
     */
    public static final String ISSUE_ADMINISTRATOR = "issueAdministrator";

    /**
     * Key (JSP) for checking the current user is a developer.
     */
    public static final String ISSUE_DEVELOPER = "issueDeveloper";

    /**
     * Key (JSP) that clarifies if a user can request to become the developer in charge for the issue.
     */
    public static final String CAN_REQUEST_WORK_OWNERSHIP = "issueDeveloperAssignee";

    private DAO dao;
    private Cipherer cipherer;
    private Decipherer decipherer;
    private Class<T> issueClass;
    private final Set<String> collections;
    private TrackerUserManager userManager;

    /**
     * Establishes the DefaultIssue collections: owners, versions, modules, developers
     */
    public IssueController() {
        collections = new HashSet<String>();
        collections.add("links");
        collections.add("voters");
        collections.add("owners");
        collections.add("creator");
        collections.add("watchers");
        collections.add("assignee");
        collections.add("comments");
        collections.add("fixVersion");
        collections.add("parentModule");
        collections.add("parentProject");
        collections.add("affectedVersion");
    }

    /**
     * Spring injection point (REQUIRED)
     *
     * @param dao a not null persistence facade
     */
    @Required public void setDao(DAO dao) {
        Assert.notNull(dao);
        this.dao = dao;
    }

    /**
     * Spring injection point (REQUIRED)
     *
     * @param cipherer a not null implementation
     */
    @Required public void setCipherer(Cipherer cipherer) {
        Assert.notNull(cipherer);
        this.cipherer = cipherer;
    }

    /**
     * Spring injection point (REQUIRED)
     *
     * @param decipherer a not null implementation
     */
    @Required public void setDecipherer(Decipherer decipherer) {
        Assert.notNull(decipherer);
        this.decipherer = decipherer;
    }

    /**
     * Spring injection point (REQUIRED)
     *
     * @param userManager a not null implementation
     */
    @Required public void setUserManager(TrackerUserManager userManager) {
        Assert.notNull(userManager);
        this.userManager = userManager;
    }

    /**
     * Spring injection point (REQUIRED)
     *
     * @param issueClass any not null instance
     */
    @Required public void setIssueClass(Class<T> issueClass) {
        Assert.notNull(issueClass);
        this.issueClass = issueClass;
    }

    /**
     * Changes the objects and collections of the target entity that will be automatically
     * initialized in the view.
     *
     * @param collections any
     */
    public void setCollectionsToInitialize(Collection<String> collections) {
        this.collections.clear();
        if (CollectionUtils.isNotEmpty(collections)) {
            this.collections.addAll(collections);
        }
    }

    /**
     * Accessor method for testing purposes.
     */
    protected Set<String> getCollections() {
        return collections;
    }

    /**
     * Gets the issue from its primary key and populates the model.
     *
     * @param uuid provided by the "issue" URL parameter
     * @param model injected by Spring MVC automatically
     *
     * @return /issue/index
     */
    @RequestMapping(method = RequestMethod.GET)
    public String fillModel(@RequestParam(value = "issue", required = true) String uuid, ModelMap model) {
        Assert.isEncrypted(decipherer, uuid);
        T issue = loadIssue(uuid);
        model.addAttribute(ISSUE, issue);
        model.addAttribute(ISSUE_VOTES, getVotes(issue));
        model.addAttribute(ISSUE_WATCHERS, getWatchers(issue));
        model.addAttribute(ISSUE_FILTERS, generateIssueFilters(issue, uuid));
        model.addAttribute(ISSUE_LAST_UPDATE, getLastModificationDate(issue));
        model.addAttribute(ISSUE_CLOSED, Issue.Status.CLOSED.equals(issue.getStatus()));
        IWebJTrackerUser user = userManager.getCurrentTrackerUser();
        model.addAttribute(ISSUE_DEVELOPER, getIsDeveloper(issue, user));
        model.addAttribute(ISSUE_ADMINISTRATOR, getIsAdministrator(issue, user));
        model.addAttribute(CAN_REQUEST_WORK_OWNERSHIP, canRequestIssueAssignment(issue, user));
        return "/issue/index";
    }

    /**
     * Retrieves a fully initialized issue from database.
     *
     * @param uuid a not null encrypted primery key
     * @return any
     */
    protected T loadIssue(String uuid) {
        T issue = dao.find(issueClass, new UUID(decipherer.decrypt(uuid)));
        return CollectionUtils.isEmpty(collections) ? issue : dao.initialize(issue, collections.toArray(new String[0]));
    }

    /**
     * Gets the date of the last operation on this issue.
     *
     * @param issue any
     * @return any
     */
    protected Date getLastModificationDate(T issue) {
        return issue == null ? null : issue.getCreationDate();
    }

    /**
     * Delegates the logic to the issue if exists.
     *
     * @param issue any
     * @return zero or more
     */
    protected Integer getVotes(T issue) {
        return issue == null ? 0 : issue.getVotes();
    }

    /**
     * Delegates the logic to the issue if exists.
     *
     * @param issue any
     * @return zero or more
     */
    protected Integer getWatchers(T issue) {
        return issue == null ? 0 : issue.getWatcherNumber();
    }

    /**
     * Generates a valid list of filters to apply to an activity grid.
     *
     * @param encUUID an encrypted UUID
     * @returna non null list
     */
    protected List<Filter> generateIssueFilters(T issue, String encUUID) {
        List<Filter> filters = new ArrayList<Filter>();
        Filter issueFilter = new Filter();
        issueFilter.setRange(false);
        issueFilter.setType(ENTITY);
        issueFilter.setPath("issue.id");
        issueFilter.setFrom(encUUID);
        filters.add(issueFilter);
        if (issue != null) {
            Project p = issue.getParentProject();
            if (p instanceof DomainEntity) {
                Filter projectFilter = new Filter();
                projectFilter.setRange(false);
                projectFilter.setType(ENTITY);
                projectFilter.setPath("project.id");
                projectFilter.setFrom(cipherer.encrypt(((DomainEntity) p).getId().toString()));
                filters.add(projectFilter);
            }
        }
        return Collections.unmodifiableList(filters);
    }

    /**
     * Checks the input data and delegates the actual logic (testing for developer rights)
     * to the actual project instance.
     *
     * @param issue any
     * @param user any
     * @return false unless the project referenced by this issue has this user as member
     */
    protected boolean getIsDeveloper(T issue, IWebJTrackerUser user) {
        Project project = getProject(issue);
        return (project != null) && (user != null) ? project.isDeveloper(user) : false;
    }

    /**
     * Checks the input data and delegates the actual logic (testing for administrator rights)
     * to the actual project instance.
     *
     * @param issue any
     * @param user any
     * @return false unless the user provided is an administrator of the issue provided.
     */
    protected boolean getIsAdministrator(T issue, IWebJTrackerUser user) {
        Project project = getProject(issue);
        return (project != null) && (user != null) ? project.isAdministrator(user) : false;
    }

    private Project getProject(T issue) {
        Project project = null;
        if (issue != null) {
            project = dao.initialize(issue.getParentProject(), "owners");
        }
        return project;
    }

    /**
     * Checks if the user can request ownership of the work to be done.
     *
     * @param issue any
     * @param user any
     * @return false unless the user is a developer at the project and the issue is not assigned to him
     */
    protected boolean canRequestIssueAssignment(T issue, IWebJTrackerUser user) {
        return getIsDeveloper(issue, user) && !user.equals(issue.getAssignee()) && !Issue.Status.CLOSED.equals(issue.getStatus());
    }

}
